iT邦幫忙

2024 iThome 鐵人賽

DAY 18
0
Security

Go!帶你探索 FIDO2 資安技術全端應用系列 第 18

【Go!帶你探索 FIDO2 資安技術全端應用】Day 18 - 實作 Apple Passkeys API (1)

  • 分享至 

  • xImage
  •  

我們在昨天將 UI 簡單設計完成後,今天就要來實作 Apple Passkeys API 了

實作 Apple Passkeys API 前要設定的配置

在實作 Apple Passkeys API 前,我們會需要設定 App 的 Associated Domains 功能

點擊「Project」->「Targets」->「Signing & Capabilities」->「+ Capability」
選擇「Associated Domains」

新增 Associated Domains Capability
▲ 新增 Associated Domains Capability

接著,在 Domains 中新增 webcredentials:<YOUR WEBAUTHN RP SERVER DOMAIN>?mode=developer

YOUR WEBAUTHN RP SERVER DOMAIN 要換成自己的 WebAuthn RP Server 的 domain

新增 webcredentials
▲ 新增 webcredentials

這邊多加 ?mode=developer 是為了可以馬上進行測試所加的,如果是在 Production 環境的話,則不需加上

實作 Apple Passkeys API

建立 PasskeysManager 物件

設定好 Capability 後,接下來要來實作 Apple Passkeys API

首先,新增一個 Manager 資料夾,並新增一個檔案叫做 PasskeysManager

接著新增一個 class 物件,叫做 PasskeysManager,並 import AuthenticationServices`,如下

import AuthenticationServices
import Foundation
import os

class PasskeysManager: NSObject {

    private let logger = Logger()
}

宣告私有常數 domain

接著宣告一個私有常數 domain,用來在 PasskeysManager 物件中使用,並在 init 中賦值。這個是用來告訴 Passkeys API,我們的 WebAuthn RP Server 的 domain 是什麼,並作為 clientDataJSON 中的 origin

import AuthenticationServices
import Foundation

class PasskeysManager: NSObject {

    private let domain: String
    
    private let logger = Logger()
    
    init(domain: String) {
        self.domain = domain
    }
}

設計 Passkeys Registration

再來要設計 Passkeys Registration 的 Function

    func registration(username: String, challenge: String, anchor: ASPresentationAnchor) {
        
        self.authenticationAnchor = anchor
        
        let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain)

        let challenge = Data(challenge.utf8)
        let userID = Data(username.utf8)
        
        let registrationRequest = publicKeyCredentialProvider.createCredentialRegistrationRequest(challenge: challenge, name: username, userID: userID)
        let authController = ASAuthorizationController(authorizationRequests: [registrationRequest])
        authController.delegate = self
        authController.presentationContextProvider = self
        authController.performRequests()
    }

設計 Passkeys Authentication

接著再來設計 Passkeys Authentication 的 Function

    func authentication(challenge: String, anchor: ASPresentationAnchor, preferImmediatelyAvailableCredentials: Bool) {
        
        self.authenticationAnchor = anchor
        
        let publicKeyCredentialProvider = ASAuthorizationPlatformPublicKeyCredentialProvider(relyingPartyIdentifier: domain)
        
        let challenge = Data(challenge.utf8)
        
        let assertionRequest = publicKeyCredentialProvider.createCredentialAssertionRequest(challenge: challenge)
        
        let authController = ASAuthorizationController(authorizationRequests: [assertionRequest])
        authController.delegate = self
        authController.presentationContextProvider = self
        
        if preferImmediatelyAvailableCredentials {
            authController.performRequests(options: .preferImmediatelyAvailableCredentials)
        } else {
            authController.performRequests()
        }
    }

實作 ASAuthorizationControllerDelegate

實作了 Passkeys Registration 和 Passkeys Authentication 這兩個 Function 後
我們還需要實作 ASAuthorizationControllerDelegate,讓 Passkeys API 執行完成後,可以透過 Delegation 的方式將 Authenticator 運算後的結果回傳給 App

下面就來實作 ASAuthorizationControllerDelegate

在 ASAuthorizationControllerDelegate 中,需要實作的 Function 有

  • func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization)
  • func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error)
extension PasskeysManager: ASAuthorizationControllerDelegate {
    
    func authorizationController(controller: ASAuthorizationController,
                                 didCompleteWithAuthorization authorization: ASAuthorization) {
        switch authorization.credential {
        case let credentialRegistration as ASAuthorizationPlatformPublicKeyCredentialRegistration:
            logger.log("A new passkey was registered: \(credentialRegistration)")
            
        case let credentialAssertion as ASAuthorizationPlatformPublicKeyCredentialAssertion:
            logger.log("A passkey was used to sign in: \(credentialAssertion)")
        
        default:
            fatalError("Received unknown authorization type.")
        }
    }
    
    func authorizationController(controller: ASAuthorizationController,
                                 didCompleteWithError error: Error) {
        guard let authorizationError = error as? ASAuthorizationError else {
            logger.error("Unexpected authorization error: \(error.localizedDescription)")
            return
        }
    }
}

實作 ASAuthorizationControllerPresentationContextProviding

上面實作了 ASAuthorizationControllerDelegate 之後,我們還需要再實作 ASAuthorizationControllerPresentationContextProviding 用來回傳當前呼叫 Passkeys API 的畫面是哪一個

在 ASAuthorizationControllerPresentationContextProviding 中,需要實作的 Function 有

  • func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor
extension PasskeysManager: ASAuthorizationControllerPresentationContextProviding {
    
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return authenticationAnchor!
    }
}

今天我們使用 Apple Passkeys API 實作了 Passkeys Registration 和 Passkeys Authentication,明天我們要來將 Authenticator 回傳給 App 的資料進行處理!


上一篇
【Go!帶你探索 FIDO2 資安技術全端應用】Day 17 - 實作一個簡單的 iOS App UI
下一篇
【Go!帶你探索 FIDO2 資安技術全端應用】Day 19 - 實作 Apple Passkeys API (2)
系列文
Go!帶你探索 FIDO2 資安技術全端應用26
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言